package com.me.chat.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.util.Optional;

/**
 * @Author VULCAN
 * @create 2020/2/6 20:54
 * @Des 授权服务器
 */
@Configuration
@EnableAuthorizationServer//我是授权服务器
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    RedisConnectionFactory factory;

    //令牌存储策略 存入redis  实现以后的 多服务集群
    @Bean
    public TokenStore tokenStore(){
        return new RedisTokenStore(factory);
    }


    @Override //访问的客户端细节配置，当请求生成一个令牌的时候，相关参数需要与该配置一致，不然无法得到令牌
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);//采用jdbc的方式
    }

    @Autowired
    PasswordEncoder passwordEncoder;//验证配置已经配过了passwordEncoder，是BCryptPasswordEncoder
    //添加数据源，动态配置客户端信息


    @Bean//jdbc方式的客户端链配置哦
    public ClientDetailsService clientDetailsService(DataSource dataSource){
        ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }

    @Autowired
    private ClientDetailsService clientDetailsService;//我在上面配置出来的↑

    @Bean//授权服务的配置
    public AuthorizationServerTokenServices tokenServices(){
        /**
         *  重写 token 创建方法，保证每次获取都是新的 token
         *  并且上一个token将会失效 这样就做到了登陆互踢的功能了
         *  不过需要注意的是，该方法仅适用于InMemoryTokenStore  对于jwt将会失效
         */

        DefaultTokenServices services = new DefaultTokenServices(){
            @Override
            @Transactional
            public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication)  {
                Optional.ofNullable(tokenStore().getAccessToken(authentication))
                        .map(accessToken -> {
                            tokenStore().removeAccessToken(accessToken);
                            return accessToken;
                        })
                        .map(OAuth2AccessToken::getRefreshToken)
                        .ifPresent(tokenStore()::removeRefreshToken);
                return super.createAccessToken(authentication);
            }
        };

        services.setClientDetailsService(clientDetailsService);
        services.setSupportRefreshToken(true);
        services.setTokenStore(tokenStore());

        return services;
    }

    @Bean//咱用数据库存授权码
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource){//授权码模式的授权码在内存中存取
        return new JdbcAuthorizationCodeServices(dataSource);                    //↓
    }
    @Autowired                                                              // ↑
    private AuthorizationCodeServices authorizationCodeServices;//授权码模式的code在内存/数据库中存取
    @Autowired
    private AuthenticationManager authenticationManager;//基于密码的认证管理器

    @Override//令牌访问端点控制，就是一些操作用的url接口
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
       endpoints
         .authenticationManager(authenticationManager)//   密码账户的认证信息
         .authorizationCodeServices(authorizationCodeServices)//授权码存储方式
               .tokenServices(tokenServices())//将tokenservice配置进来
        .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许post方式访问令牌
    }


    //配置令牌安全策略模式
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")  //所有人可以访问token
                .checkTokenAccess("permitAll()") //检测令牌公开接口 任何资源服务都可以检测资源
                .allowFormAuthenticationForClients(); //允许表单认证
    }
}
